﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfGenetic.Fitness;
using WpfGenetic.Tests;

namespace WpfGenetic.Decoders
{
    public static class IntegerPermutation
    {
        public static void Start(string cryptoText, GeneticConfiguration config, out string text,
                                 object lockObj, CancellationToken token, Action<string>? logDelegate = null,
                                 Action<string>? resultDelegate = null, Action<double>? topFitnessDelegate = null,
                                 Func<string>? getTimeNow = null)
        {
            text = "";

            if (cryptoText is null || cryptoText.Length == 0) { throw new ArgumentException("Зашифрованный текст пустой"); }
            if (config is null) { throw new ArgumentException("Конфигурация отсутствует"); }

            Random rand = new(DateTime.UtcNow.Millisecond);

            // Составляем 0-ую популяцию
            List<PermutationIndividual> individuals = PopulationCreator(config.POPULATION_SIZE, config.HORIZONTAL_MIN_KEY_LENGTH, 
                                                                        config.HORIZONTAL_MAX_KEY_LENGTH, config.VERTICAL_MIN_KEY_LENGTH,
                                                                        config.VERTICAL_MAX_KEY_LENGTH, config.RANK_HORIZONTAL_KEY_LENGTH,
                                                                        config.RANK_VERTICAL_KEY_LENGTH);

            List<double> FitnessValues;                 //Список приспособленностей особей
            List<double> maxFitnessValues = [];         // Максимальная приспособленность в текущей популяции
            List<double> meanFitnessValues = [];        //Средняя приспособленность в текущей популяции

            // Считаем фитнесс-функцию
            CalculateFitness(individuals, cryptoText, config.Alphabet, bigramsType: config.BIGRAMS_TYPE, trigramsType: config.TRIGRAMS_TYPE);

            // Считаем максимальную и среднюю фитнесс функцию
            FitnessValues = [];
            foreach (PermutationIndividual individual in individuals)
            {
                FitnessValues.Add(individual.Fitness);
            }
            maxFitnessValues.Add(FitnessValues.Max());
            meanFitnessValues.Add(FitnessValues.Sum() / individuals.Count);

            int generationCounter = 0;  // Счётчик поколений

            #region For tests
            // Строка с результатами тестирования
            string tests = "";
            // Процент совпадения с исходным текстом
            double percentage = 0;
            #endregion

            while (generationCounter < config.MAX_GENERATIONS &&
                   !token.IsCancellationRequested &&
                   percentage < 0.99)
            {
                //Блокирующий объект, необходим для приостановки работы алгоритма
                lock (lockObj)
                {
                    generationCounter++;

                    // Турнирный отбор
                    TournamentSelection(individuals, out List<PermutationIndividual> selected, config.SIZE_TOURNAMENT);

                    // Количество скрещиваний
                    int crossoverCount = 0;
                    int selectedSize = selected.Count;
                    while (selected.Count < config.POPULATION_SIZE)
                    {
                        int i = rand.Next(selectedSize);
                        int j = rand.Next(selectedSize);
                        // Двое родителей скрещиваются и получается потомок
                        if (i != j && rand.Next(1, 101) <= config.P_CROSSOVER)
                        {
                            CrossingOverOnePoint(selected[i], selected[j], out PermutationIndividual child);

                            // Проверяем, что такой особи ещё нет в популяции (смотрим именно по числовому представлению)
                            bool flag = true;
                            int count = 0;
                            for (int k = 0; k < selected.Count; k++)
                            {
                                if (child.IntVerticalKey.SequenceEqual(selected[k].IntVerticalKey) &&
                                    child.IntHorizontalKey.SequenceEqual(selected[k].IntHorizontalKey))
                                {
                                    count++;
                                    if (count > config.MAX_REPETITION_OF_INDIVIDUALS)
                                    {
                                        flag = false;
                                        break;
                                    }
                                }
                            }

                            if (flag)
                            {
                                crossoverCount++;
                                child.Generation = generationCounter;
                                selected.Add(child);
                            }
                        }
                    }

                    // Мутируем особей
                    int mutationCount = 0;
                    for (int i = 0; i < selected.Count; i++)
                    {
                        if (rand.Next(1, 101) <= config.P_MUTATION && selected[i].Status != IndividualStatus.SelectedTop)
                        {
                            mutationCount++;

                            Mutation(selected[i].IntHorizontalKey, selected[i].HorizontalLength, config.P_KEY_MUTATION, config.P_LENGTH_MUTATION, config.P_RAISE_MUTATION,
                                selected[i].IntHorizontalKey.Count == 1 ? 1 : selected[i].IntHorizontalKey.Count / 2);

                            Mutation(selected[i].IntVerticalKey, selected[i].VerticalLength, config.P_KEY_MUTATION, config.P_LENGTH_MUTATION, config.P_RAISE_MUTATION, 
                                selected[i].IntVerticalKey.Count == 1 ? 1 : selected[i].IntVerticalKey.Count / 2);

                            selected[i].Status = IndividualStatus.Mutation;
                        }
                    }

                    // Обновляем список популяции
                    individuals = selected;

                    // Считаем фитнесс-функцию
                    CalculateFitness(individuals, cryptoText, config.Alphabet, bigramsType: config.BIGRAMS_TYPE, trigramsType: config.TRIGRAMS_TYPE);

                    FitnessValues = [];
                    foreach (PermutationIndividual individual in individuals)
                    {
                        FitnessValues.Add(individual.Fitness);
                    }
                    maxFitnessValues.Add(FitnessValues.Max());
                    meanFitnessValues.Add(FitnessValues.Sum() / individuals.Count);

                    // Выделяем лучших особей
                    if (config.COUNT_OF_MAX_SAVE > 0)
                    {
                        individuals = [.. individuals.OrderByDescending((p) => p.Fitness)];

                        // Вывод суммы биграмм и триграмм лучшей особи на график
                        if (topFitnessDelegate is not null)
                        {
                            topFitnessDelegate(individuals[0].FrequencyOfBigrams + individuals[0].FrequencyOfTrigrams);
                        }

                        for (int i = 0; i < config.COUNT_OF_MAX_SAVE && i < individuals.Count; i++)
                        {
                            individuals[i].Status = IndividualStatus.Top;
                        }
                    }

                    if (generationCounter % 100 == 0 && logDelegate is not null)
                    {
                        // Выводим данные в лог
                        string log = $"Поколение {generationCounter}, Максимальная приспособленность {maxFitnessValues[^1]}, Средняя приспособленность {meanFitnessValues[^1]}, " +
                            $"Количество скрещиваний {crossoverCount}, Количество мутаций {mutationCount}\n" +
                            GetLogText(individuals.OrderByDescending(i => i.Fitness).Take(config.CONSOLE_WRITTEN_COUNT).ToList(), cryptoText, config.Alphabet) + "\n\n";

                        logDelegate(log);
                    }

                    #region For testig
                    // Сохраняем значения совпадения текстов для тестирования
                    if (generationCounter % 10 == 0)
                    {
                        var individual = individuals.OrderByDescending(i => i.Fitness).FirstOrDefault();

                        if (individual is not null)
                        {
                            DecryptPermutation(cryptoText, individual.IntHorizontalKey, individual.IntVerticalKey, out string decryptText);

                            percentage = Testing.PercentageKeyMatching(Testing.HKey1000, Testing.VKey1000, individual.IntHorizontalKey, individual.IntVerticalKey);
                        }


                        tests += "\n" + generationCounter.ToString() + "\t" +
                                 percentage.ToString() + "\t" +
                                 //Testing.GetTests(individuals.OrderByDescending(i => i.Fitness).Take(1).ToList(), cryptoText, config.Alphabet) +
                                 getTimeNow?.Invoke();
                    }
                    #endregion
                }
            }

            // Отправляем результаты лучшей особи
            if (resultDelegate is not null)
            {
                resultDelegate(GetLogText(individuals.OrderByDescending(i => i.Fitness).Take(1).ToList(), cryptoText, config.Alphabet) + "\n" +
                                tests);
            }

            // В случае отмены задачи выбрасываем исключение
            if (token.IsCancellationRequested)
            {
                token.ThrowIfCancellationRequested();
            }

        }

        /// <summary>
        /// Создаёт список индивидов с ключом из чисел
        /// </summary>
        /// <param name="count"></param>
        /// <param name="horizontalMinLength"></param>
        /// <param name="horizontalMaxLength"></param>
        /// <param name="verticalMinLength"></param>
        /// <param name="verticalMaxLength"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        public static List<PermutationIndividual> PopulationCreator(int count = 1, int horizontalMinLength = 1,
                                                                    int horizontalMaxLength = 1, int verticalMinLength = 1,
                                                                    int verticalMaxLength = 1, int maxRankHorizontal = 1,
                                                                    int maxRankVertical = 1)
        {
            if (count < 1) { throw new ArgumentException("Популяция не может быть меньше 1"); }
            if (horizontalMinLength > horizontalMaxLength || verticalMinLength > verticalMaxLength) { throw new ArgumentException("Максимальная длина ключа не может быть меньше минимальной длины ключа"); }

            List<PermutationIndividual> population = [];

            Random rand = new(DateTime.UtcNow.Millisecond);
            int randomLength;
            List<int> horizontalKey, verticalKey;

            // Числа для составления ключей
            List<int> index;
            int k;  // Буфер для числа ключа
            for (int i = 0; i < count; i++)
            {
                // Горизонтальный ключ
                // Выбрать случайную длину ключа
                randomLength = rand.Next(horizontalMinLength, horizontalMaxLength + 1);
                horizontalKey = new List<int>(randomLength);

                index = new(randomLength);
                for (int j = 0; j < randomLength; j++) { index.Add(j); }

                // Собираем ключ из возможных чисел
                for (int j = 0; j < randomLength; j++)
                {
                    k = rand.Next(index.Count);
                    horizontalKey.Add(index[k]);
                    index.RemoveAt(k);
                }

                // Вертикальный ключ
                // Выбрать случайную длину ключа
                randomLength = rand.Next(verticalMinLength, verticalMaxLength + 1);
                verticalKey = new List<int>(randomLength);

                index = new(randomLength);
                for (int j = 0; j < randomLength; j++) { index.Add(j); }

                // Собираем ключ из возможных чисел
                for (int j = 0; j < randomLength; j++)
                {
                    k = rand.Next(index.Count);
                    verticalKey.Add(index[k]);
                    index.RemoveAt(k);
                }

                population.Add(new PermutationIndividual()
                                    {
                                        IntHorizontalKey = horizontalKey,
                                        HorizontalLength = new Binary(horizontalKey.Count, maxRankHorizontal),
                                        IntVerticalKey = verticalKey,
                                        VerticalLength = new Binary(verticalKey.Count, maxRankVertical),
                                        Status = IndividualStatus.New,
                                    });
            }
            return population;
        }

        /// <summary>
        /// Вычисляет фитнесс функцию по формуле frequencyOfBigrams / max(frequencyOfBigrams)
        /// и заполняет соответствующие поля в индивидах
        /// Предварительно текст дешифруется ключом индивида
        /// </summary>
        /// <param name="population"></param>
        /// <param name="cryptoText"></param>
        /// <param name="alphabet"></param>
        /// <param name="bigramsType"></param>
        /// <exception cref="ArgumentException"></exception>
        public static void CalculateFitness(List<PermutationIndividual>? population, string? cryptoText, string? alphabet, int? bigramsType = null, int? trigramsType = null)
        {
            if (population is null || population.Count == 0) throw new ArgumentException("Популяция не может быть пустой");
            if (cryptoText is null || cryptoText.Length == 0) throw new ArgumentException("Текст не может быть пустым");
            if (alphabet is null || alphabet.Length == 0) throw new ArgumentException("Алфавит не может быть пустым");

            double maxBigrams = 1;
            double maxTrigrams = 1;
            foreach (PermutationIndividual individual in population)
            {
                if (individual.Status != IndividualStatus.SelectedTop &&
                    individual.Status != IndividualStatus.Selected)
                {
                    DecryptPermutation(cryptoText, individual.IntHorizontalKey, individual.IntVerticalKey, out string text);

                    if (bigramsType is not null)
                    {
                        FrequencyOfBigrams.FindFrequencyOfBigrams(text, out double frequencyOfBigrams, (int)bigramsType);
                        individual.FrequencyOfBigrams = frequencyOfBigrams;

                        if (maxBigrams < frequencyOfBigrams) maxBigrams = frequencyOfBigrams;
                    }
                    else { maxBigrams = 1; }

                    if (trigramsType is not null)
                    {
                        FrequencyOfTrigrams.FindFrequencyOfTrigrams(text, out double frequencyOfTrigrams, (int)trigramsType);
                        individual.FrequencyOfTrigrams = frequencyOfTrigrams;

                        if (maxTrigrams < frequencyOfTrigrams) maxTrigrams = frequencyOfTrigrams;
                    }
                    else { maxTrigrams = 1; }
                }
            }

            foreach (Individual individual in population)
            {
                individual.Fitness = (individual.FrequencyOfBigrams / maxBigrams) +
                                     (individual.FrequencyOfTrigrams / maxTrigrams);
            }
        }

        /// <summary>
        /// Алгоритм: 1 - разбивает текст на строки по длине вертикального ключа; 
        /// 2 - текст вписывается построчно; 
        /// 3 - в каждой строке перемешиваются буквы в соответствии с горизонтальным ключом; 
        /// 4 - строки перемешиваются в соответствии с вертикальным ключом; 
        /// 5 - текст выписывается по столбцам
        /// cmdLog - если true, то производиться вывод действий в консоль
        /// </summary>
        /// <param name="cryptoText"></param>
        /// <param name="horizontalKey"></param>
        /// <param name="verticalKey"></param>
        /// <param name="alphabet"></param>
        /// <param name="text"></param>
        /// <param name="cmdLog"></param>
        /// <exception cref="ArgumentException"></exception>
        public static void DecryptPermutation(string? cryptoText, List<int>? horizontalKey, List<int>? verticalKey, out string text)
        {
            text = "";
            if (cryptoText is null || verticalKey is null || horizontalKey is null) { throw new ArgumentException("Null reference"); }

            int horizontalKeyLength = horizontalKey.Count;
            int verticalKeyLength = verticalKey.Count;

            if (horizontalKeyLength == 0 || verticalKeyLength == 0) { throw new ArgumentException("Один из ключей является пустым"); }
             
            int MAX_LENGTH_MESSAGE = horizontalKeyLength * verticalKeyLength;   // Максимальная длина сообщения = произведения длин ключей

            var cryptoBuild = new StringBuilder(cryptoText.Length); // Строитель итогового текста, будет увеличиваться зашифрованными блоками
            StringBuilder realBlock;                                // Копия блока текста
            
            StringBuilder newHorizontalTextBuilder;     // Буфер для изменения блока по столбцам
            StringBuilder newVerticalTextBuilder;       // Буфер для изменения блока по строкам
            StringBuilder bufCryptoText;                // Буфер для изменения при чтении по столбцам

            // Основной цикл расшифровки, каждая итерация расшифровывает один блок текста
            for (int i = 0; i < cryptoText.Length; i += MAX_LENGTH_MESSAGE)
            {
                // Запишем зашифрованный текст построчно, а лишнее забьём символом dash (-)

                realBlock = new StringBuilder(MAX_LENGTH_MESSAGE);
                // Копируем блок текста
                for (int j = i; j < i + MAX_LENGTH_MESSAGE; j++)
                {
                    if (j < cryptoText.Length) realBlock.Append(cryptoText[j]);
                    else realBlock.Append('|');
                }

                // Восстановим чтение по столбцам
                bufCryptoText = new(MAX_LENGTH_MESSAGE);
                for (int j = 0; j < MAX_LENGTH_MESSAGE; j++)
                {
                    bufCryptoText.Append('_');
                }
                for (int h = 0; h < horizontalKeyLength; h++)
                {
                    for (int v = 0; v < verticalKeyLength; v++)
                    {
                        bufCryptoText[v * horizontalKeyLength + h] = realBlock[h * verticalKeyLength + v];
                    }
                }

                // Восстановим последовательность строк
                newVerticalTextBuilder = new(MAX_LENGTH_MESSAGE);
                for (int j = 0; j < MAX_LENGTH_MESSAGE; j++)
                {
                    newVerticalTextBuilder.Append('+');
                }
                for (int v = 0; v < verticalKeyLength; v++)
                {// Каждую строчку
                    int k = verticalKey[v];  // Номер строки где находится i-ая строка
                    for (int h = 0; h < horizontalKeyLength; h++)
                    {// Переставляем посимвольно
                        newVerticalTextBuilder[v * horizontalKeyLength + h] =
                            bufCryptoText[k * horizontalKeyLength + h];
                    }
                }

                // Восстановим последовательность символов в каждой строке
                newHorizontalTextBuilder = new(MAX_LENGTH_MESSAGE);
                for (int j = 0; j < MAX_LENGTH_MESSAGE; j++)
                {
                    newHorizontalTextBuilder.Append('*');
                }
                for (int v = 0; v < verticalKeyLength; v++)
                {// Для каждой строчки
                    for (int h = 0; h < horizontalKeyLength; h++)
                    {// Для каждого символа строки
                        int k = horizontalKey[h]; // Номер буквы входного текста, где находится j-ый символ
                        newHorizontalTextBuilder[v * horizontalKeyLength + h] =
                            newVerticalTextBuilder[v * horizontalKeyLength + k];
                    }
                }

                // В newHorizontalTextBuilder лежит новый дешифрированный блок
                cryptoBuild.Append(newHorizontalTextBuilder);
            }

            text = cryptoBuild.ToString();

        }

        /// <summary>
        /// Производит турнирный отбор особей по значению фитнесс функции
        /// size - Численность турнира можно задать (сколько особей будет соревноваться)
        /// countOfMaxSafe - количество лучших особей, которые в лубом случае остаются и не участвуют в турнире
        /// </summary>
        /// <param name="population"></param>
        /// <param name="selected"></param>
        /// <param name="size"></param>
        /// <exception cref="ArgumentException"></exception>
        public static void TournamentSelection(List<PermutationIndividual>? population, out List<PermutationIndividual> selected, int size = 2)
        {
            selected = [];

            if (population is null || population.Count == 0) throw new ArgumentException("Популяция не может быть пустой");
            if (size < 2) throw new ArgumentException("Численность турнира не может быть меньше двух");

            // Индексы всей популяции
            List<int> index = [];
            for (int i = 0; i < population.Count; i++)
            {
                if (population[i].Status != IndividualStatus.Top)
                {
                    index.Add(i);
                }
                else
                {
                    population[i].Status = IndividualStatus.SelectedTop;
                    selected.Add(population[i]);
                }

            }

            // Индексы индивидов, участвующих в турнире 
            List<int> sizeIndex;
            double maxFitness;
            Random rand = new(DateTime.UtcNow.Millisecond);
            PermutationIndividual individual = new();

            while (index.Count > 0)
            {
                maxFitness = 0;
                sizeIndex = [];

                // Если особей осталось меньше или равное численности турнира, значит участвуют все оставшиеся
                if (index.Count <= size)
                {
                    for (int i = 0; i < index.Count; i++)
                    {
                        sizeIndex.Add(index[i]);
                    }
                    // Обнуляем индексы
                    index = [];
                }
                else
                {
                    int j;
                    // Выбираем случайных size особей
                    for (int i = 0; i < size; i++)
                    {
                        j = rand.Next(index.Count);
                        sizeIndex.Add(index[j]);
                        index.RemoveAt(j);
                    }
                }

                // Сам турнирный отбор
                for (int i = 0; i < sizeIndex.Count; ++i)
                {
                    if (population[sizeIndex[i]].Fitness > maxFitness)
                    {
                        maxFitness = population[sizeIndex[i]].Fitness;
                        individual = population[sizeIndex[i]];
                    }
                }

                individual.Status = IndividualStatus.Selected;
                selected.Add(individual);
            }
        }

        /// <summary>
        /// Осуществляет скрещивание двух особей, в результате чего появляется 1 ребёнок
        /// Длина ключей и чьи ключи берутся за основу определяется вероятностями
        /// </summary>
        /// <param name="parent1"></param>
        /// <param name="parent2"></param>
        /// <param name="child"></param>
        /// <exception cref="ArgumentException"></exception>
        public static void CrossingOverOnePoint(PermutationIndividual parent1, PermutationIndividual parent2, out PermutationIndividual child)
        {
            child = new();

            int childLengthHorizontal, childLengthVertical;

            // Перемешиваем длины ключей родителей
            child.HorizontalLength = Binary.Shuffle(parent1.HorizontalLength, parent2.HorizontalLength);
            child.VerticalLength = Binary.Shuffle(parent1.VerticalLength, parent2.VerticalLength);

            // Представляем бинарный вид в десятичный
            childLengthHorizontal = child.HorizontalLength.GetInt();
            childLengthVertical = child.VerticalLength.GetInt();

            if (childLengthHorizontal == 0)
            {
                childLengthHorizontal = 1;
                child.HorizontalLength[0] = true;
            }

            if (childLengthVertical == 0)
            {
                childLengthVertical = 1;
                child.VerticalLength[0] = true;
            }

            // Инициализируем списки для создания ключей
            child.IntHorizontalKey = new(childLengthHorizontal);
            child.IntVerticalKey = new(childLengthVertical);

            // Если длина ключа ребёнка больше или равна ключу родителя
            // Создаём ребёнка первая половина символов от первого родителя,
            // далее пустые клетки заполняются числами 2ого родителя,
            // если эти числа ещё не использовались и они входят в диапазон длины потомка,
            // оставшиеся пустые клетки заполняются случайным образом недостающими числами 

            // Выровненные до размера ребёнка ключи родителей
            List<int> p1H = new(childLengthHorizontal);
            List<int> p1V = new(childLengthVertical);
            List<int> p2H = new(childLengthHorizontal);
            List<int> p2V = new(childLengthVertical);

            #region Выравниваем размеры ключей ребенка и родителей

            if (childLengthHorizontal >= parent1.IntHorizontalKey.Count)
            {
                // Значит все числа родителя могут поместиться в ребёнке
                // Копируем числа родителя
                for (int i = 0; i < parent1.IntHorizontalKey.Count; i++)
                {
                    p1H.Add(parent1.IntHorizontalKey[i]);
                }

                // Забиваем оставшимися возможными числами
                for (int i = parent1.IntHorizontalKey.Count; i < childLengthHorizontal; i++)
                {
                    p1H.Add(i);
                }
            }
            else
            {
                // Надо убрать из родителя все лишние числа
                for (int i = 0; i < parent1.IntHorizontalKey.Count;  i++)
                {
                    if (parent1.IntHorizontalKey[i] < childLengthHorizontal)
                    {
                        p1H.Add(parent1.IntHorizontalKey[i]);
                    }
                }
            }

            if (childLengthHorizontal >= parent2.IntHorizontalKey.Count)
            {
                // Значит все числа родителя могут поместиться в ребёнке
                // Копируем числа родителя
                for (int i = 0; i < parent2.IntHorizontalKey.Count; i++)
                {
                    p2H.Add(parent2.IntHorizontalKey[i]);
                }

                // Забиваем оставшимися возможными числами
                for (int i = parent2.IntHorizontalKey.Count; i < childLengthHorizontal; i++)
                {
                    p2H.Add(i);
                }
            }
            else
            {
                // Надо убрать из родителя все лишние числа
                for (int i = 0; i < parent2.IntHorizontalKey.Count; i++)
                {
                    if (parent2.IntHorizontalKey[i] < childLengthHorizontal)
                    {
                        p2H.Add(parent2.IntHorizontalKey[i]);
                    }
                }
            }

            if (childLengthVertical >= parent1.IntVerticalKey.Count)
            {
                // Значит все числа родителя могут поместиться в ребёнке
                // Копируем числа родителя
                for (int i = 0; i < parent1.IntVerticalKey.Count; i++)
                {
                    p1V.Add(parent1.IntVerticalKey[i]);
                }

                // Забиваем оставшимися возможными числами
                for (int i = parent1.IntVerticalKey.Count; i < childLengthVertical; i++)
                {
                    p1V.Add(i);
                }
            }
            else
            {
                // Надо убрать из родителя все лишние числа
                for (int i = 0; i < parent1.IntVerticalKey.Count; i++)
                {
                    if (parent1.IntVerticalKey[i] < childLengthVertical)
                    {
                        p1V.Add(parent1.IntVerticalKey[i]);
                    }
                }
            }

            if (childLengthVertical >= parent2.IntVerticalKey.Count)
            {
                // Значит все числа родителя могут поместиться в ребёнке
                // Копируем числа родителя
                for (int i = 0; i < parent2.IntVerticalKey.Count; i++)
                {
                    p2V.Add(parent2.IntVerticalKey[i]);
                }

                // Забиваем оставшимися возможными числами
                for (int i = parent2.IntVerticalKey.Count; i < childLengthVertical; i++)
                {
                    p2V.Add(i);
                }
            }
            else
            {
                // Надо убрать из родителя все лишние числа
                for (int i = 0; i < parent2.IntVerticalKey.Count; i++)
                {
                    if (parent2.IntVerticalKey[i] < childLengthVertical)
                    {
                        p2V.Add(parent2.IntVerticalKey[i]);
                    }
                }
            }

            #endregion

            // Половина ключа
            int halfLengthHorizontal = childLengthHorizontal / 2;
            if (halfLengthHorizontal == 0) halfLengthHorizontal = 1;

            int halfLengthVertical = childLengthVertical / 2;
            if (halfLengthVertical == 0) halfLengthVertical = 1;

            // Первая половина ключей
            // Горизонтальный ключ
            for (int i = 0; i < halfLengthHorizontal; i++)
            {
                child.IntHorizontalKey.Add(p1H[i]);
                p2H.Remove(p1H[i]);
            }
            // Вертикальный ключ
            for (int i = 0; i < halfLengthVertical; i++)
            {
                child.IntVerticalKey.Add(p1V[i]);
                p2V.Remove(p1V[i]);
            }

            // Из второго родителя удалены все использованные числа, поэтому спокойно заполняем оставшиеся

            // Вторая половина ключей
            // Горизонтальный ключ
            for (int i = halfLengthHorizontal; i < childLengthHorizontal; i++)
            {
                child.IntHorizontalKey.Add(p2H[0]);
                p2H.RemoveAt(0);
            }
            // Вертикальный ключ
            for (int i = halfLengthVertical; i < childLengthVertical; i++)
            {
                child.IntVerticalKey.Add(p2V[0]);
                p2V.RemoveAt(0);
            }

            child.Status = IndividualStatus.New;
        }

        /// <summary>
        /// Мутация заключается в обмене местами некоторых чисел ключа
        /// pKey - Вероятность того, что при мутации части ключа поменяются местами
        /// pLength - Вероятность того, что при мутации измениться длина ключа
        /// pRaise - Вероятность того, что при изменении длины ключа она увеличится на 1 (дополнительный символ берётся из алфавита и встаёт в конец ключа), иначе у ключа удаляется последний символ (если изначально длина 1 ничего не происходит)
        /// </summary>
        /// <param name="individual"></param>
        /// <param name="alphabet"></param>
        /// <param name="pKey"></param>
        /// <param name="pLength"></param>
        /// <param name="pRaise"></param>
        /// <exception cref="ArgumentException"></exception>
        public static void Mutation(List<int> key, Binary binLength, double pKey, double pLength, double pRaise, int countKey = 1)
        {
            if (key is null || key.Count == 0) throw new ArgumentException("Ключ не может быть пустым");
            if (countKey < 0) throw new ArgumentException("Количество изменяемых символов ключа не может быть меньше 0");

            Random rand = new(DateTime.UtcNow.Millisecond);

            // Меняем длину ключа
            if (rand.Next(1, 101) <= pLength)
            {
                // Повышаем
                if (rand.Next(1, 101) <= pRaise)
                {
                    int newInt = key.Count;
                    key.Add(newInt);
                    binLength.Inc();
                }
                // Или понижаем
                else if (key.Count > 1)
                {
                    // Удаляем элемент с самым большим числом
                    key.Remove(key.Count - 1);
                    binLength.Dec();
                }
            }

            // Мутация для одного числа не имеет смысла
            if (key.Count >= 2)
            {
                // Меняем позиции ключа
                for (int i = 0; i < countKey; i += 2)
                {
                    if (rand.Next(0, 101) <= pKey)
                    {
                        int fIndex = rand.Next(0, key.Count);
                        int sIndex = rand.Next(0, key.Count);

                        (key[sIndex], key[fIndex]) = (key[fIndex], key[sIndex]);
                    }
                }
            }
        }

        private static string GetLogText(List<PermutationIndividual> individuals, string cryptoText, string alphabet)
        {
            if (cryptoText is null || cryptoText.Length == 0) throw new ArgumentException("Текст не может быть пустым");
            if (alphabet is null || alphabet.Length == 0) throw new ArgumentException("Алфавит не может быть пустым");

            string log = "";
            string VKey, HKey;
            int number = 0;

            foreach (PermutationIndividual individual in individuals)
            {
                number++;
                HKey = ""; VKey = "";
                foreach (int i in individual.IntHorizontalKey) { HKey += i + ","; }
                foreach (int i in individual.IntVerticalKey) { VKey += i + ","; }

                DecryptPermutation(cryptoText, individual.IntHorizontalKey, individual.IntVerticalKey, out string text);
                log += $"{number}) Вертикальный ключ = '{VKey}'; Горизонтальный ключ = '{HKey}'; " +
                    $"Поколение = '{individual.Generation}'; Биграммы: '{individual.FrequencyOfBigrams}'; " +
                    $"Триграммы: '{individual.FrequencyOfTrigrams}'\nФитнесс функция: '{individual.Fitness}';\n " +
                    $"Дешифрованная строка = '{text}'\n\n";
            }
            return log;
        }



    }
}
